package com.lingo.a2b.common.util;

import com.lingo.a2b.common.handler.ValueConvertHandler;
import com.lingo.a2b.model.bo.ClassInfo;
import com.lingo.a2b.model.bo.FieldInfo;
import com.lingo.a2b.model.entity.ConnectFieldRelative;
import io.swagger.annotations.ApiModelProperty;
import lombok.SneakyThrows;
import org.apache.logging.log4j.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author lingo
 * @date 2020/5/21 15:54
 * @Company: www.xyb2b.com
 */

public class ReflectionUtils {

    private static final Logger logger = LoggerFactory.getLogger(ReflectionUtils.class);
    private static final List<String> IGNORE_FIELD_LIST = Arrays.asList("serialVersionUID");

    public static ClassInfo getClassInfo(Class cls, List<ConnectFieldRelative> relativeList) {
        return getClassInfo(cls, relativeList, Strings.EMPTY);
    }

    private static ClassInfo getClassInfo(Class cls, List<ConnectFieldRelative> relativeList, String preName) {
        ClassInfo info = new ClassInfo();
        info.setShortName(cls.getSimpleName());
        info.setFullName(cls.getName());
        info.setFieldInfoList(listField(cls, relativeList, preName));
        info.setPackageName(cls.getPackage().getName());
        info.setSuperClass(cls != Object.class && cls.getSuperclass() != null ? cls.getSuperclass().getName() : null);
        return info;
    }

    private static List<FieldInfo> listField(Class cls, List<ConnectFieldRelative> relativeList, String preName) {
        List<FieldInfo> fatherFields = null;
        Class superclass = cls.getSuperclass();
        if (superclass != Object.class && superclass != null) {
            fatherFields = listField(superclass, relativeList, preName);
        }
        Field[] declaredFields = cls.getDeclaredFields();
        Map<String, ConnectFieldRelative> map = CollectionUtils.isEmpty(relativeList) ? null : relativeList.stream().collect(Collectors.toMap(ReflectionUtils::getFieldKey, Function.identity()));
        List<FieldInfo> fieldInfoList = Arrays.stream(declaredFields).filter(field -> !IGNORE_FIELD_LIST.contains(field.getName())).map(field -> getFieldInfo(cls, map, relativeList, field, preName)).collect(Collectors.toList());
        if (!CollectionUtils.isEmpty(fatherFields)) {
            fieldInfoList.addAll(fatherFields);
        }
        return fieldInfoList;
    }

    private static FieldInfo getFieldInfo(Class locateClass, Map<String, ConnectFieldRelative> relativeMap, List<ConnectFieldRelative> relativeList, Field field, String preName) {
        String name = field.getName();
        String meaning = null;
        ApiModelProperty annotation = field.getAnnotation(ApiModelProperty.class);
        if (annotation != null) {
            meaning = annotation.value();
        }
        FieldInfo fieldInfo = new FieldInfo(locateClass.getName(), name, PackUtils.packNestedFieldName(preName, name), meaning);
        Class<?> fieldType = field.getType();
        if (Collection.class.isAssignableFrom(fieldType)) {
            fieldType = getRealType(field);
            fieldInfo.setFisCollection(true);
        }
        // 如果不是自定义的 javaBean，则uap
        if (!isJdkOrNumberClass(fieldType)) {
            fieldInfo.setFieldClassInfo(getClassInfo(fieldType, relativeList, PackUtils.packNestedFieldName(preName, name)));
        }
        //加上数据库里面的东西
        if (!CollectionUtils.isEmpty(relativeMap) && relativeMap.containsKey(getFieldKey(fieldInfo))) {
            ConnectFieldRelative value = relativeMap.get(getFieldKey(fieldInfo));
            fieldInfo.setTargetName(value.getFtargetFieldName());
            fieldInfo.setEnable(value.getFenable());
            fieldInfo.setFvalueConverterId(value.getFvalueConvertId());
        }else{
            fieldInfo.setEnable(false);
            fieldInfo.setFvalueConverterId(ValueConvertHandler.NO_CONVERT);
        }
        return fieldInfo;
    }

    /**
     * 提取List 里面的泛型具体类型
     *
     * @param field 想要提取的field
     * @return
     */
    public static Class<?> getRealType(Field field) {
        Type genericType = field.getGenericType();
        boolean isSupportedGenericType = (genericType instanceof ParameterizedType) && (((ParameterizedType) genericType).getActualTypeArguments()[0] instanceof Class);
        if (!isSupportedGenericType) {
            logger.error("字段：{}的List 的泛型参数:{}不支持！(非 ParameterizedType 实例)", field.getName(), genericType.getTypeName());
            throw new RuntimeException();
        }
        ParameterizedType pt = (ParameterizedType) genericType;
        //得到泛型里的class类型对象
        Class<?> innerClass = (Class<?>) pt.getActualTypeArguments()[0];
        return innerClass;
    }

    public static boolean isJdkOrNumberClass(Class clazz) {
        return clazz != null && (clazz.getClassLoader() == null || Number.class.isAssignableFrom(clazz));
    }

    public static Map<String, Field> getClassFieldMap(Class cls) {
        Field[] declaredFields = cls.getDeclaredFields();
        Map<String, Field> map = new HashMap<>(declaredFields.length);
        for (Field field : declaredFields) {
            map.put(field.getName(), field);
        }
        return map;
    }

    @SneakyThrows
    public static Object getValue(Field field, Object data) {
        boolean accessible = field.isAccessible();
        field.setAccessible(true);
        Object o = field.get(data);
        field.setAccessible(accessible);
        return o;
    }

    private static String getFieldKey(String locateClassName, String fieldName) {
        return locateClassName + fieldName;
    }

    private static String getFieldKey(ConnectFieldRelative connectFieldRelative) {
        return getFieldKey(connectFieldRelative.getFlocateClassName(), connectFieldRelative.getFsrcFieldName());
    }

    private static String getFieldKey(FieldInfo connectFieldRelative) {
        return getFieldKey(connectFieldRelative.getLocateClassName(), connectFieldRelative.getDbName());
    }
}
